package com.dly.blog.aop;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import com.dly.blog.annotation.OpLog;
import com.dly.blog.domain.OpLogEntity;
import com.dly.blog.enums.OpType;
import com.dly.blog.event.OpLogEvent;
import com.dly.blog.exception.LogExceptionEnum;
import com.dly.blog.msg.R;
import com.dly.blog.utile.*;
import io.micrometer.common.util.StringUtils;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jodd.util.StringPool;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.security.SecurityUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Optional;

/**
 * @Author: DaiLiYu
 * @Date: 2023/7/17 10:35
 */
@Slf4j
@Aspect
public class OpLogAspect {

    private static final ThreadLocal<OpLogEntity> OP_LOG_DTO_THREAD_LOCAL = new ThreadLocal<>();

    private static final String FORM_DATA_CONTENT_TYPE = "multipart/form-data";

    @Pointcut("@annotation(com.dly.blog.annotation.OpLog) || @within(com.dly.blog.annotation.OpLog)")
    public void opLogAspect(){}

    /**
     * 正常返回
     * @param joinPoint
     * @param ret
     */
    @AfterReturning(returning = "ret", pointcut = "opLogAspect()")
    public void doAfterReturning(JoinPoint joinPoint, Object ret){
        OpLog opLogAnnotation = getOpLogAnnotation(joinPoint);
        if(!opLogAnnotation.enabled()){
            return;
        }
        R<?> r = null;
        if(ret instanceof ResponseEntity<?> responseEntity){
            r = Convert.convert(R.class, responseEntity.getBody());
        }
        OpLogEntity opLogEntity = get();

        if(r == null) {
            opLogEntity.setType(OpType.OPT);
            if(opLogAnnotation.response()) {
                opLogEntity.setResult(StringPool.EMPTY);
            }
        } else {
            if(r.getCode() == 200) {
                //正常
                opLogEntity.setType(OpType.OPT);
            } else {
                //异常
                opLogEntity.setType(OpType.EX);
                opLogEntity.setExceptionDetails(r.getMsg());
            }
            if(opLogAnnotation.response()) {
                opLogEntity.setResult(JsonUtils.writeToJson(r));
            }
        }
        //发布事件
        publishEvent(opLogEntity);
    }

    /**
     * 异常返回
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(pointcut = "opLogAspect()", throwing = "e")
    public void doAfterReturning(JoinPoint joinPoint, Exception e){
        OpLog opLogAnnotation = getOpLogAnnotation(joinPoint);
        if(!opLogAnnotation.enabled()){
            return;
        }

        OpLogEntity opLogEntity = this.get();
        opLogEntity.setType(OpType.EX);

        //遇到错误时，请求参数若为空，则记录
        if(!opLogAnnotation.request() && StringUtils.isEmpty(opLogEntity.getParams())) {
            Object[] args = joinPoint.getArgs();
            HttpServletRequest request = HttpServletRequestUtils.getRequest();
            String strArgs = getArgs(args, request);
            opLogEntity.setParams(strArgs);
        }

        //异常对象
        opLogEntity.setExceptionDetails(e.getMessage());

        publishEvent(opLogEntity);
    }

    private void publishEvent(OpLogEntity opLogEntity) {
        //设置完成时间 以及时间差
        opLogEntity.setFinishTime(LocalDateTime.now());
        opLogEntity.setConsumingTime(opLogEntity.getStartTime().until(opLogEntity.getFinishTime(), ChronoUnit.MILLIS));
        //设置其他信息 todo
        SpringContextHolder.publishEvent(new OpLogEvent(opLogEntity));
        OP_LOG_DTO_THREAD_LOCAL.remove();
    }

    /**
     * 截取指定长度的字符串
     *
     * @param val 参数
     * @return 截取文本
     */
    private String getText(String val) {
        return StrUtil.sub(val, 0, 65535);
    }

    @Before(value = "opLogAspect()")
    public void doBefore(JoinPoint joinPoint){
        OpLog opLogAnnotation = getOpLogAnnotation(joinPoint);
        if(!opLogAnnotation.enabled()){
            return;
        }
        // 构建操作日志数据
        OpLogEntity opLogDTO = buildOptLogEntity(joinPoint, opLogAnnotation);
        OP_LOG_DTO_THREAD_LOCAL.set(opLogDTO);
    }

    public OpLogEntity buildOptLogEntity(JoinPoint joinPoint, OpLog opLog){
        OpLogEntity opLogEntity = get();
        //获取请求
        HttpServletRequest request = HttpServletRequestUtils.getRequest();

        //如果是认证相关的接口放行
        if(opLog.hasAuth()) {
            //设置操作人
            opLogEntity.setCreateUser(SecurityUtils.getUserId());
            opLogEntity.setUserName(SecurityUtils.getUsername());
        }

        opLogEntity.setDescription(opLogEntity.getDescription());
        //设置类名称
        opLogEntity.setClassPath(joinPoint.getTarget().getClass().getName());
        //设置方法名称
        opLogEntity.setActionMethod(joinPoint.getSignature().getName());
        //设置日志描述
        opLogEntity.setLogDescription(opLog.value());

        //设置参数
        setParams(joinPoint, opLog, opLogEntity, request);

        //设置ip地址
        opLogEntity.setRequestIp(WebUtils.getClientIp(request));
        //设置请求路径
        opLogEntity.setRequestUri(request.getRequestURI());
        //设置请求方法
        opLogEntity.setHttpMethod(request.getMethod());
        //设置开始事件
        opLogEntity.setStartTime(LocalDateTime.now());
        return opLogEntity;
    }

    /**
     * 设置参数
     * @param joinPoint
     * @param opLog
     * @param opLogEntity
     * @return
     */
    @NonNull
    private void setParams(JoinPoint joinPoint, OpLog opLog, OpLogEntity opLogEntity, HttpServletRequest httpServletRequest) {
        // 参数
        Object[] args = joinPoint.getArgs();

        if (opLog.request()) {
            String strArgs = getArgs(args, httpServletRequest);
            opLogEntity.setParams(getText(strArgs));
        }
    }

    private String getArgs(Object[] args, HttpServletRequest request) {
        String strArgs = StringPool.EMPTY;
        Object[] params = Arrays.stream(args).filter(item -> !(item instanceof ServletRequest || item instanceof ServletResponse)).toArray();
        try {
            //不是表单类型
            if (!request.getContentType().contains(FORM_DATA_CONTENT_TYPE)) {
                strArgs = JsonUtils.writeToJson(params);
            }
        } catch (Exception e) {
            try {
                strArgs = Arrays.toString(params);
            } catch (Exception ex) {
                log.warn("解析参数异常", ex);
            }
        }
        return strArgs;
    }

    private OpLogEntity get() {
        OpLogEntity opLogDTO = OP_LOG_DTO_THREAD_LOCAL.get();
        if (opLogDTO == null) {
            return new OpLogEntity();
        }
        return opLogDTO;
    }

    private OpLog getOpLogAnnotation(JoinPoint point){
        OpLog opLog = null;
        try {
            if(point.getSignature() instanceof MethodSignature methodSignature){
                Method method =
                        methodSignature.getMethod();
                if(method!= null){
                    opLog = method.getAnnotation(OpLog.class);
                    //从类上获取该注解
                    if(opLog == null){
                       opLog = method.getDeclaringClass().getAnnotation(OpLog.class);
                    }
                }
            }
            return opLog;
        } catch (Exception e){
            log.debug("获取 {}.{} 的@OpLog注解失败",point.getSignature().getDeclaringType(), point.getSignature().getName(), e);
            throw LogExceptionEnum.GET_OPLOG_ANNOTATION.getFileException();
        }
    }
}
